#![feature(trivial_bounds)]

use anchor_client::{anchor_lang::prelude::Signer, Client};
use anchor_lang::{system_program, AnchorDeserialize, AnchorSerialize};
use convert_case::{Case, Casing};
use serde::{Deserialize, Serialize};
use serde_json::{json, Map};
use sha2::Digest;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
    account::ReadableAccount,
    commitment_config::{self, CommitmentConfig},
    pubkey::Pubkey,
    signature::{Keypair, Signature},
};
use solana_transaction_status::UiTransactionEncoding;
use std::{collections::HashMap, rc::Rc, str::FromStr};

pub fn get_idl(rpc_client: &RpcClient, pid: &str) -> Idl {
    let pid = Pubkey::from_str(pid).unwrap();

    let base = Pubkey::find_program_address(&[], &pid).0;

    let idl = Pubkey::create_with_seed(&base, "anchor:idl", &pid).unwrap();

    let account_info = rpc_client.get_account(&idl).unwrap();
    let mut acc_data = &account_info.data[8..account_info.data.len()];
    let idl = IDLBorsh::deserialize(&mut acc_data).unwrap();

    let binding = inflate::inflate_bytes_zlib(&idl.data).unwrap();
    let idl_str = std::str::from_utf8(&binding).unwrap();

    serde_json::from_str::<Idl>(idl_str).unwrap()
}

#[derive(AnchorDeserialize, AnchorSerialize, Debug)]
pub struct IDLBorsh {
    pub authority: Pubkey,
    pub data: Vec<u8>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct Idl {
    pub version: String,
    pub name: String,

    pub docs: Option<Vec<String>>,
    pub constants: Option<Vec<IdlConst>>,
    pub instructions: Vec<IdlInstruction>,
    pub state: Option<IdlState>,
    pub accounts: Vec<IdlTypeDefinition>,
    pub types: Vec<IdlTypeDefinition>,
    pub events: Option<Vec<IdlEvent>>,
    pub errors: Option<Vec<IdlErrorCode>>,
    pub metadata: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlConst {
    pub name: String,
    #[serde(rename = "type")]
    pub ty: IdlType,
    pub value: String,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlState {
    #[serde(rename = "struct")]
    pub strct: IdlTypeDefinition,
    pub methods: Vec<IdlInstruction>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlInstruction {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub docs: Option<Vec<String>>,
    pub accounts: Vec<IdlAccountItem>,
    pub args: Vec<IdlField>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub returns: Option<IdlType>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlAccounts {
    pub name: String,
    pub accounts: Vec<IdlAccountItem>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum IdlAccountItem {
    IdlAccount(IdlAccount),
    IdlAccounts(IdlAccounts),
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlAccount {
    pub name: String,
    pub is_mut: bool,
    pub is_signer: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub docs: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub pda: Option<IdlPda>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlPda {
    pub seeds: Vec<IdlSeed>,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub program_id: Option<IdlSeed>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase", tag = "kind")]
pub enum IdlSeed {
    Const(IdlSeedConst),
    Arg(IdlSeedArg),
    Account(IdlSeedAccount),
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlSeedAccount {
    #[serde(rename = "type")]
    pub ty: IdlType,
    // account_ty points to the entry in the "accounts" section.
    // Some only if the `Account<T>` type is used.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub account: Option<String>,
    pub path: String,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlSeedArg {
    #[serde(rename = "type")]
    pub ty: IdlType,
    pub path: String,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlSeedConst {
    #[serde(rename = "type")]
    pub ty: IdlType,
    pub value: serde_json::Value,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlField {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub docs: Option<Vec<String>>,
    #[serde(rename = "type")]
    pub ty: IdlType,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlEvent {
    pub name: String,
    pub fields: Vec<IdlEventField>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlEventField {
    pub name: String,
    #[serde(rename = "type")]
    pub ty: IdlType,
    pub index: bool,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlTypeDefinition {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub docs: Option<Vec<String>>,
    #[serde(rename = "type")]
    pub ty: IdlTypeDefinitionTy,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase", tag = "kind")]
pub enum IdlTypeDefinitionTy {
    Struct { fields: Vec<IdlField> },
    Enum { variants: Vec<IdlEnumVariant> },
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlEnumVariant {
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub fields: Option<EnumFields>,
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum EnumFields {
    Named(Vec<IdlField>),
    Tuple(Vec<IdlType>),
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum IdlType {
    Bool,
    U8,
    I8,
    U16,
    I16,
    U32,
    I32,
    F32,
    U64,
    I64,
    F64,
    U128,
    I128,
    Bytes,
    String,
    PublicKey,
    Defined(String),
    Option(Box<IdlType>),
    Vec(Box<IdlType>),
    Array(Box<IdlType>, usize),
}

impl std::str::FromStr for IdlType {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut s = s.to_string();
        fn array_from_str(inner: &str) -> IdlType {
            match inner.strip_suffix(']') {
                None => {
                    let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
                    let ty = IdlType::from_str(raw_type).unwrap();
                    let len = raw_length.replace('_', "").parse::<usize>().unwrap();
                    IdlType::Array(Box::new(ty), len)
                }
                Some(nested_inner) => array_from_str(&nested_inner[1..]),
            }
        }
        s.retain(|c| !c.is_whitespace());

        let r = match s.as_str() {
            "bool" => IdlType::Bool,
            "u8" => IdlType::U8,
            "i8" => IdlType::I8,
            "u16" => IdlType::U16,
            "i16" => IdlType::I16,
            "u32" => IdlType::U32,
            "i32" => IdlType::I32,
            "f32" => IdlType::F32,
            "u64" => IdlType::U64,
            "i64" => IdlType::I64,
            "f64" => IdlType::F64,
            "u128" => IdlType::U128,
            "i128" => IdlType::I128,
            "Vec<u8>" => IdlType::Bytes,
            "String" | "&str" => IdlType::String,
            "Pubkey" => IdlType::PublicKey,
            _ => match s.to_string().strip_prefix("Option<") {
                None => match s.to_string().strip_prefix("Vec<") {
                    None => {
                        if s.to_string().starts_with('[') {
                            array_from_str(&s)
                        } else {
                            IdlType::Defined(s.to_string())
                        }
                    }
                    Some(inner) => {
                        let inner_ty = Self::from_str(
                            inner
                                .strip_suffix('>')
                                .ok_or_else(|| anyhow::anyhow!("Invalid option"))?,
                        )?;
                        IdlType::Vec(Box::new(inner_ty))
                    }
                },
                Some(inner) => {
                    let inner_ty = Self::from_str(
                        inner
                            .strip_suffix('>')
                            .ok_or_else(|| anyhow::anyhow!("Invalid option"))?,
                    )?;
                    IdlType::Option(Box::new(inner_ty))
                }
            },
        };
        Ok(r)
    }
}

#[derive(Debug, Clone, Serialize, AnchorDeserialize, AnchorSerialize, Deserialize, PartialEq)]
pub struct IdlErrorCode {
    pub code: u32,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none", default)]
    pub msg: Option<String>,
}
